package com.xuelangyun.form.common.redis;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


/**
 * RedisOperateUtils 工具类
 *
 * @author weiqing.hk
 * @version 1.0
 * @date 2024-05-17 21:12
 */
@Component
public class RedisOperateUtils {

    //是否开启redis缓存  true开启   false关闭
    @Value("${spring.redis.open: false}")
    private boolean open = false;

    @Autowired(required = false)
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 缓存过期配置
     * key- 过期时间
     */
    final static Map<String, Long> cache_expire = new ConcurrentHashMap<String, Long>();
    final static Map<String, Object> cache = new ConcurrentHashMap<String, Object>();


    /**
     * 默认过期时长，单位：秒
     */
    public static long DEFAULT_EXPIRE = 60 * 60;
    /**
     * 不设置过期时长
     */
    public final static long NOT_EXPIRE = -1;

    /**
     * 默认30ms尝试一次
     */
    private final static long LOCK_TRY_INTERVAL = 30L;
    /**
     * 默认尝试20s
     */
    private final static long LOCK_TRY_TIMEOUT = 1 * 1000L;
    /**
     * 单个业务持有锁的时间30s，防止死锁
     */
    private final static long LOCK_EXPIRE = 30 * 1000L;


    /**
     * set
     *
     * @param key
     * @param value
     * @param expire
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void set(String key, Object value, long expire) {
        if (key == null || value == null) {
            return;
        }
        if (!open) {
            //System.out.println("key : " + key + ",value:" + value );
            if (DEFAULT_EXPIRE <= 0) {
                return;
            }
            cache.put(key, value);
            if (expire < 0) {
                expire = DEFAULT_EXPIRE;
            }
            cache_expire.put(key, System.currentTimeMillis() + expire * 1000);
            return;
        }

        if (value instanceof java.util.List) {
            List data = (List) value;
            if (data.isEmpty()) return;
            //key如果存在则覆盖
            redisTemplate.opsForValue().set(key, value);
        } else if (value instanceof Set) {
            Set data = (Set) value;
            if (data.isEmpty()) return;
            redisTemplate.opsForSet().add(key, data.toArray());
        } else if (value instanceof Map) {

            Map map = (Map) value;

            if (map.isEmpty()) return;
            redisTemplate.opsForHash().putAll(key, (Map) value);
        } else {

            redisTemplate.opsForValue().set(key, value);
        }
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }

    /**
     * 重新设置key的过期时间
     *
     * @param key
     * @param expire
     */
    public void expire(String key, int expire) {
        if (key == null) return;
        if (!open) {
            cache_expire.put(key, System.currentTimeMillis() + expire * 1000);
        } else {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public Map<String, Object> getCache() {
        return cache;
    }

    @SuppressWarnings("unchecked")
    public <T> T get(String key, Class<T> clazz, long expire) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return null;
            }
            clearExpire();
            cache.get(key);
            return null;
        }

        DataType dtype = redisTemplate.type(key);
        if (clazz.equals(List.class)) {
            return (T) redisTemplate.opsForValue().get(key);
        } else if (clazz == Set.class || dtype == DataType.SET) {
            return (T) redisTemplate.opsForSet().members(key);
        } else if (clazz == Map.class || dtype == DataType.HASH) {
            return (T) redisTemplate.opsForHash().entries(key);
        } else {
            return (T) redisTemplate.opsForValue().get(key);
        }
    }


    public void set(String key, Object value) {
        if (!open) {
            set(key, value, DEFAULT_EXPIRE);
            return;
        }
        set(key, value, DEFAULT_EXPIRE);
    }

    @SuppressWarnings("unchecked")
    public <T> T get(String key, Class<T> clazz) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return null;
            }
            clearExpire();
            return (T) cache.get(key);
        }
        return get(key, clazz, NOT_EXPIRE);
    }


    public Object get(String key) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return null;
            }
            clearExpire();
            return cache.get(key);
            //return null;
        }

        DataType dtype = redisTemplate.type(key);

        if (dtype == DataType.LIST) {
            return redisTemplate.opsForList().range(key, 0, -1);
        } else if (dtype == DataType.SET) {
            return redisTemplate.opsForSet().members(key);
        } else if (dtype == DataType.HASH) {
            return redisTemplate.opsForHash().entries(key);
        } else {
            return redisTemplate.opsForValue().get(key);
        }

    }

    public boolean containKey(String key) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return false;
            }
            clearExpire();
            return cache.containsKey(key);
        }

        return redisTemplate.hasKey(key);
        //return false;
    }

    public void delete(String key) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return;
            }
            cache.remove(key);
            cache_expire.remove(key);
            return;
        }
        redisTemplate.delete(key);
    }

    public void deleteCollections(Collection<String> keys) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return;
            }
            clearExpire();
            for (String key : keys) {
                cache.remove(key);
            }

            return;
        }
        redisTemplate.delete(keys);
    }

    public void deleteContains(String str) {
        deleteContains(str, null);
    }

    public Set<String> patternKeys(String pattern) {
        if (!open) {
            clearExpire();

            Set<String> keys = new HashSet<String>();
            for (String key : cache.keySet()) {
                if (key.matches(pattern)) {
                    keys.add(key);
                }
            }
            return keys;
        }
        return redisTemplate.keys(pattern);
    }


    public void deleteContains(String str, ICacheFilter<String> keyFilter) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return;
            }
            clearExpire();
            String pattern = ".*" + str + ".*";
            Set<String> keys = cache.keySet();
            if (keys != null) for (String key : keys) {
                if (key.matches(pattern) && (null == null || keyFilter.yes(key))) {

                    cache.remove(key);
                }
            }

            return;
        }
        String pattern = "*" + str + "*";

        Set<String> keys = patternKeys(pattern);
        if (keys != null && !keys.isEmpty()) {

            if (keyFilter != null) keys = keys.stream().filter(t -> keyFilter.yes(t)).collect(Collectors.toSet());

            deleteCollections(keys);
        }

    }


    public void deleteStartKey(String startKey) {
        if (!open) {
            if (DEFAULT_EXPIRE <= 0) {
                return;
            }
            String pattern = startKey + ".*";
            clearExpire();
            Set<String> keys = cache.keySet();
            if (keys != null) for (String key : keys) {
                if (key.matches(pattern)) {
                    System.out.println("clear cache key: " + key);
                    cache.remove(key);
                }
            }
            return;
        }
        String pattern = startKey + "*";
        Set<String> keys = patternKeys(pattern);
        if (keys != null && !keys.isEmpty()) {
            deleteCollections(keys);
        }

    }

    public String arrayToString(Object[] objs) {

        if (objs == null || objs.length == 0) {
            return "";
        }
        StringBuffer sb = new StringBuffer();
        for (Object o : objs) {
            sb.append(o == null ? "" : o.toString());
            sb.append(",");
        }
        return sb.toString();
    }

    public String mapToString(Map<String, Object> map) {

        if (map == null || map.isEmpty()) {
            return "";
        }

        Set<String> keys_ = map.keySet();
        String[] keys = keys_.toArray(new String[0]);
        // sort
        Arrays.sort(keys);
        StringBuffer sb = new StringBuffer();

        for (String key : keys) {
            Object v = map.get(key);
            String vstr = v != null ? v.toString() : "";
            sb.append(key + "." + vstr + ";");
        }

        if (sb.length() > 100) {
            // 取hash 然后 前后和1/2 1/4 1/8位得字符串
            StringBuffer ret = new StringBuffer();
            ret.append(sb.length() + "_" + sb.charAt(0) + "" + sb.charAt(sb.length() - 1));

            // 长度
            int len = sb.length();
            for (int i = 2; i < 50; i++) {
                char c = sb.charAt(len / i);
                if (c == 13 || c == 10 || c == 32) continue;
                ret.append(c);
            }
            return ret.toString();
        }
        return sb.toString();

    }

    public synchronized void clearExpire() {

        if (DEFAULT_EXPIRE <= 0) {
            return;
        }
        long ct = System.currentTimeMillis();

        String[] keys = cache_expire.keySet().toArray(new String[0]);

        for (String key : keys) {
            Long time = cache_expire.get(key);
            if (time != null && time < ct) {
                cache.remove(key);
                cache_expire.remove(key);
            }
        }
    }


    /**
     * 获取锁
     *
     * @param key     锁键值
     * @param timeout 超时时间
     * @param time    全局锁生命周期
     * @param unit    时间单位
     * @return 是否获取到锁
     */
    private boolean getLock(String key, long timeout, long time, TimeUnit unit) {
        try {
            long startTimeMillis = System.currentTimeMillis();
            do {
                long newValue = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
                Boolean isOk = redisTemplate.opsForValue().setIfAbsent(key, newValue);
                if (isOk) {
                    // 获得锁
                    redisTemplate.expire(key, time, unit);
                    return true;
                }
                // 获取过期时间
                Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
                if (null == oldExpireTime) {
                    oldExpireTime = 0L;
                }
                if (oldExpireTime >= System.currentTimeMillis()) {
                    // 不小于系统时间并且过了超时时间，则不获取锁
                    if ((System.currentTimeMillis() - startTimeMillis) > timeout) {
                        return false;
                    }
                    System.out.println("thread sleep");
                    // 休眠
                    Thread.sleep(LOCK_TRY_INTERVAL);
                }
                // 新的过期时间
                long newExpireTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
                Long currentExpireTime = (Long) redisTemplate.opsForValue().getAndSet(key, newExpireTime);
                if (null == currentExpireTime) {
                    currentExpireTime = 0L;
                }
                if (currentExpireTime.equals(oldExpireTime)) {
                    // 获取到锁
                    redisTemplate.expire(key, time, unit);
                    return true;
                }
            } while (true);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取锁
     *
     * @param key 锁Key
     * @return 是否获取锁
     */
    public boolean lock(String key) {
        return getLock(key, 0, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取锁
     *
     * @param key        锁Key
     * @param expire     有效期
     * @param expireUnit 有效期时间单位
     * @return 是否获取锁
     */
    public boolean lock(String key, long expire, TimeUnit expireUnit) {
        return getLock(key, 0, expire, expireUnit);
    }

    /**
     * 尝试获取锁
     *
     * @param key 锁Key
     * @return 是否获取锁
     */
    public boolean tryLock(String key) {
        return tryLock(key, LOCK_TRY_TIMEOUT, TimeUnit.MILLISECONDS);
    }

    /**
     * 尝试获取锁
     *
     * @param key     锁Key
     * @param timeout 等待超时时间
     * @param unit    等待超时时间单位
     * @return 是否获取锁
     */
    public boolean tryLock(String key, long timeout, TimeUnit unit) {
        // 超时时间转成毫秒
        timeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
        return getLock(key, timeout, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
    }

    /**
     * 尝试获取锁
     *
     * @param key         锁Key
     * @param timeout     等待超时时间
     * @param timeoutUnit 等待超时时间单位
     * @param expire      有效期
     * @param expireUnit  有效期时间单位
     * @return
     */
    public boolean tryLock(String key, long timeout, TimeUnit timeoutUnit, long expire, TimeUnit expireUnit) {
        // 超时时间转成毫秒
        timeout = TimeUnit.MILLISECONDS.convert(timeout, timeoutUnit);
        return getLock(key, timeout, expire, expireUnit);
    }

    /**
     * 释放锁
     *
     * @param key 锁Key
     */
    public void unlock(String key) {
        Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
        if (null != oldExpireTime && oldExpireTime >= System.currentTimeMillis()) {
            // 大于过期时间，则删除key
            redisTemplate.delete(key);
        }
    }

}
